#include<QDebug>
#include "mainwindow.h"
#include <QtSerialPort/QSerialPort>
#include <QtSerialPort/QSerialPortInfo>
#include "./ui_mainwindow.h"



MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    deviceState = DeviceState::Stopped;
    shouldSaveData = false;
    saveDataPathPrefix = "C:\\";
    initialized = false;

    receive_data.resize(4);
    this->dialog = new BaudRateDialog(this);

    setupUI();
    updateUI();


    this->displayAllSerialPortInfo();
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::startBtnClicked(){
    qDebug() << "start btn clicked, open device " << this->ui->portNames->currentText() << " at rate "
             << this->baudRate;

    this->dataPointsCnt = 0;
    this->serialPort = new QSerialPort(this);
    this->serialPort->setPortName(this->ui->portNames->currentText());
    this->serialPort->setBaudRate(this->baudRate);


    connect(this->serialPort, &QSerialPort::readyRead, this, &MainWindow::processData);


    if(!this->serialPort->open(QIODevice::ReadOnly)) {
        qDebug() << "this serial port open failed";
    }

    this->magicSkipped = false;
    this->deviceState = DeviceState::Running;
    updateUI();
    QVector<double> x_data, y_data;
    for(int i = 0; i< 4 ; i ++ ){
        ui->rawDataPlot->graph(i)->setData(x_data, y_data);
    }
    for(int i = 0; i< 3 ; i ++ ){
        ui->sumDataPlot->graph(i)->setData(x_data, y_data);
    }
    ui->fourQuadPlot->graph(0)->setData(x_data, y_data);

    ui->rawDataPlot->xAxis->setRange(0, this->initialRangeSize);
    ui->sumDataPlot->xAxis->setRange(0, this->initialRangeSize);
    ui->rawDataPlot->setInteractions(QCP::iRangeZoom | QCP::iSelectAxes |
                                     QCP::iSelectLegend | QCP::iSelectPlottables);
    ui->sumDataPlot->setInteractions(QCP::iRangeZoom | QCP::iSelectAxes |
                                     QCP::iSelectLegend | QCP::iSelectPlottables);
}

void MainWindow::stopBtnClicked(){
    qDebug() << "stop button clicked";

    if(DeviceState::Running != deviceState) {
        qDebug() << "not in running state";
        return;
    }

    if(this->serialPort->isOpen()){
        this->serialPort->close();
    }

    disconnect(this->serialPort, &QSerialPort::readyRead, this, &MainWindow::processData);
    this->serialPort = nullptr;
    deviceState = DeviceState::Stopped;

    this->magicSkipped = false;

    ui->rawDataPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes |
                                     QCP::iSelectLegend | QCP::iSelectPlottables);

    ui->sumDataPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes |
                                     QCP::iSelectLegend | QCP::iSelectPlottables);
    updateUI();
}

void MainWindow::abnormalStop(){
    qDebug() << "this is abnormal stop, will call stopBtnClicked";
    this->stopBtnClicked();
}

void MainWindow::chooseDirectoryBtnClicked(){
    QString dir = QFileDialog::getExistingDirectory(this, tr("Open Directory"),
                                                    saveDataPathPrefix,
                                                    QFileDialog::ShowDirsOnly
                                                        | QFileDialog::DontResolveSymlinks);
    ui->saveDirPathLineEdit->setText(dir);
    saveDataPathPrefix = dir;
}

void MainWindow::saveDataCheckBoxChecked(){
    shouldSaveData = ui->saveCheckbox->isChecked();
    updateSaveWidgets();
}

void MainWindow::processData(){
    if(this->deviceState == DeviceState::Stopped) {
        return;
    }

    char peeked[40];
    char data[20];

    auto isValidBuf = [=](char buf[20]) {
        return ((buf[16] & 0xff) == 0x00) &&
               ((buf[17] & 0xff) == 0x00) &&
               ((buf[18] & 0xff) == 0x80) &&
               ((buf[19] & 0xff) == 0x7f);
    };

    auto calPadding = [=](char buf[40]) {
        const char trailing[4] = {0x00, 0x00, 0x80, 0x7f};
        for(int i=0; i < (40 - 4) ; i ++) {
            if(memcmp(trailing, buf + i, 4) == 0){
                return (i + 4) % 20;
            }
        }

        // omit all bytes if no match
        return 40;
    };

    for(qint8 i = 0; i < 4; i++){
        this->receive_data[i].clear();
    }

    int size = 0;

    while(true) {
        qint64 left = this->serialPort->bytesAvailable();
        // if data less then 36, wait for next round
        if(left < 40) {
            if(size == 0 ) {
                return;
            }else{
                break;
            }
        }

        this->serialPort->peek(peeked, 40);
        this->serialPort->read(calPadding(peeked));

        qint64 readCnt = this->serialPort->read(data, 20);
        if(readCnt != 20){
            qDebug() << "reading count is " << readCnt << " which is " << (readCnt == 20 ? " normal " : "abnormal ");
        }

        float values[4];
        memcpy(&values, data, 16);
        //this->displayHexAndDecodeFloats(data, 20);

        // put data into receive_data
        for(int i = 0; i < 4; i ++) {
            this->receive_data[i].append(values[i] * 0.0003125);
        }

        size += 1;
    }

    QVector<double> tmp_sums(size);
    QVector<double> tmp_xdiffs(size);
    QVector<double> tmp_ydiffs(size);
    QVector<double> X(size);
    QVector<double> Y(size);


    for (int i = 0; i < size; ++i) {
        double d1 = qMax(this->receive_data[0].at(i), 0.0);
        double d2 = qMax(this->receive_data[1].at(i), 0.0);
        double d3 = qMax(this->receive_data[2].at(i), 0.0);
        double d4 = qMax(this->receive_data[3].at(i), 0.0);

        if (d1 == 0.0 ) {
            d1 = 1;
        }
        if (d2 == 0.0 ) {
            d2 = 1;
        }
        if (d3 == 0.0 ) {
            d3 = 1;
        }
        if (d4 == 0.0 ) {
            d4 = 1;
        }

        tmp_xdiffs[i] = (d1 + d4) - (d2 + d3) ;
        tmp_ydiffs[i] = (d1 + d2) - (d3 + d4);
        tmp_sums[i] = (d1 + d2 + d3 + d4);
        X[i] = tmp_xdiffs[i] / tmp_sums[i];
        Y[i] = tmp_ydiffs[i] / tmp_sums[i];
    }

    xdiffs.append(tmp_xdiffs);
    ydiffs.append(tmp_ydiffs);
    sums.append(tmp_sums);

    QVector<double> x_data(size);
    for (int var = 0; var < size; ++var) {
        x_data[var] = (var + this->dataPointsCnt) ;
    }

    for (int var = 0; var < DEFAULT_CH_CNT; ++var) {
        for(int i = 0; i < size; i ++ ) {
            ui->rawDataPlot->graph(var)->addData(x_data[i], receive_data[var][i]);
        }

    }

    if(x_data.first() > 1000) {
        ui->rawDataPlot->xAxis->moveRange(size);
    }

    for(int i=0; i< size; i ++ ) {
        ui->sumDataPlot->graph(0)->addData(x_data[i], xdiffs[i]);
        ui->sumDataPlot->graph(1)->addData(x_data[i], ydiffs[i]);
        ui->sumDataPlot->graph(2)->addData(x_data[i], sums[i]);
    }
    if(x_data.first() > 1000) {
        ui->sumDataPlot->xAxis->moveRange(size);
    }

    if(shouldSaveData) {
        for(int var = 0; var < DEFAULT_CH_CNT; ++var) {
            saveData(QString("CH_%1").arg(var + 1), receive_data[var], this->saveFormat());
        }
        saveData("XDiffs", xdiffs, this->saveFormat());
        saveData("YDiffs", ydiffs, this->saveFormat());
        saveData("Sums", sums, this->saveFormat());
    }

    ui->rawDataPlot->replot(QCustomPlot::rpQueuedReplot);
    ui->sumDataPlot->replot(QCustomPlot::rpQueuedReplot);

    xdiffs.clear();
    ydiffs.clear();
    sums.clear();

    QVector<double> lastX, lastY;
    lastX.append(X[size-1]);
    lastY.append(Y[size-1]);
    ui->xDisplay->setText(QString("%1").arg(X.last()));
    ui->yDisplay->setText(QString("%1").arg(Y.last()));
    ui->xdiffDisplay->setText(QString("%1").arg(xdiffs.last()));
    ui->ydiffDisplay->setText(QString("%1").arg(ydiffs.last()));
    ui->sumDisplay->setText(QString("%1").arg(sums.last()));
    ui->fourQuadPlot->graph(0)->setData(lastX, lastY);
    ui->fourQuadPlot->replot();

    this->dataPointsCnt += size;
}

SaveFormat MainWindow::saveFormat() {
    if(ui->binRadioButton->isChecked()){
        return SaveFormat::BINFormat;
    }

    return SaveFormat::CSVFormat;
}

void MainWindow::updateSerialPortSettings(){
    switch(deviceState){
    case DeviceState::Running:
        ui->startBtn->setEnabled(false);
        ui->stopBtn->setEnabled(true);
        ui->portNames->setEnabled(false);
        ui->baudRate->setEnabled(false);
        break;
    case DeviceState::Stopped:
        ui->startBtn->setEnabled(true);
        ui->stopBtn->setEnabled(false);
        ui->portNames->setEnabled(true);
        ui->baudRate->setEnabled(true);
        break;
    default:
        break;
    }
}

void MainWindow::updateSaveWidgets(){
    if(shouldSaveData) {
        ui->chooseDirPathBtn->setEnabled(true);
    }else{
        ui->chooseDirPathBtn->setEnabled(false);
    }

    ui->saveDirPathLineEdit->setReadOnly(true);
}

void MainWindow::updateUI(){
    updateSerialPortSettings();
    updateSaveWidgets();
}

void MainWindow::setupUI() {
    // port names
    foreach(const QSerialPortInfo info, QSerialPortInfo::availablePorts()) {
        ui->portNames->addItem(info.portName());
    }

    QStringList list = {
        "自定义",
        "110",
        "300",
        "600",
        "1200",
        "2400",
        "4800",
        "9600",
        "14400",
        "38400",
        "56000",
        "57600",
        "115200",
        "128000",
        "230400",
        "256000",
        "460800",
        "500000",
        "512000",
        "600000",
        "750000",
        "921600",
        "1000000",
        "1500000",
        "2000000",
    };

    for(int i = list.size()- 1; i >= 0;  i--) {
        this->ui->baudRate->addItem(list[i]);
    }


   this->ui->dataBits->addItem("8");
   this->ui->parity->addItem("NoParity");
   this->ui->stopBits->addItem("OneStop");

    ui->csvRadioButton->setChecked(true);
    ui->binRadioButton->setChecked(false);

    setupRawData();
    setupSumData();
    setupFourQuad();
    connectSlots();
}

using namespace QColorConstants;
void MainWindow::setupRawData(){
    ui->rawDataPlot->xAxis->setRange(0, initialRangeSize);
    ui->rawDataPlot->yAxis->setLabel(QString("电压 / V"));
    ui->rawDataPlot->yAxis->setVisible(true);
    ui->rawDataPlot->xAxis->setLabel(QString(""));
    ui->rawDataPlot->xAxis->setVisible(true);
    ui->rawDataPlot->yAxis->setRange(-10, 10);
    ui->rawDataPlot->xAxis->setScaleType(QCPAxis::stLinear);


    for(int i = 0; i< DEFAULT_CH_CNT; i ++ ){
        ui->rawDataPlot->addGraph();
    }

    QVector<QColor> colors{
        Red,
        Green,
        Cyan,
        Blue,
    };

    for(int i = 0; i< DEFAULT_CH_CNT; i ++ ){
        ui->rawDataPlot->graph(i)->setAdaptiveSampling(true);
        ui->rawDataPlot->graph(i)->setName(QString("CH %1").arg(i + 1));
        ui->rawDataPlot->graph(i)->setPen(QPen(colors[i]));
    }

    ui->rawDataPlot->setInteractions(QCP::iRangeZoom | QCP::iSelectAxes |
                                     QCP::iSelectLegend | QCP::iSelectPlottables);

    ui->rawDataPlot->legend->setVisible(true);
    QFont legendFont = font();  // start out with MainWindow's font..
    legendFont.setPointSize(6); // and make a bit smaller for legend
    ui->rawDataPlot->legend->setFont(legendFont);
    ui->rawDataPlot->legend->setBrush(QBrush(QColor(255,255,255,230)));
    ui->rawDataPlot->axisRect()->insetLayout()->setInsetAlignment(0, Qt::AlignTop|Qt::AlignRight);
}

void MainWindow::setupSumData(){
    // 200k
    ui->sumDataPlot->xAxis->setRange(0, initialRangeSize);
    ui->sumDataPlot->yAxis->setRange(-20, 20);
    ui->sumDataPlot->yAxis->setLabel(QString("电压 / V"));
    ui->sumDataPlot->yAxis->setVisible(true);
    ui->sumDataPlot->xAxis->setLabel(QString(""));
    ui->sumDataPlot->xAxis->setVisible(true);
    ui->sumDataPlot->xAxis->setScaleType(QCPAxis::stLinear);

    for(int i = 0; i< 3; i ++ ){
        ui->sumDataPlot->addGraph();
    }

    QRandomGenerator generator(QTime::currentTime().msecsSinceStartOfDay() );
    ui->sumDataPlot->graph(0)->setName("XDiffs");
    ui->sumDataPlot->graph(0)->setAdaptiveSampling(true);
    ui->sumDataPlot->graph(0)->setPen(QPen(Red));

    ui->sumDataPlot->graph(1)->setName("YDiffs");
    ui->sumDataPlot->graph(1)->setAdaptiveSampling(true);
    ui->sumDataPlot->graph(1)->setPen(QPen(Green));

    ui->sumDataPlot->graph(2)->setName("Sum");
    ui->sumDataPlot->graph(2)->setAdaptiveSampling(true);
    ui->sumDataPlot->graph(2)->setPen(QPen(Cyan));

    ui->sumDataPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes |
                                     QCP::iSelectLegend | QCP::iSelectPlottables);

    ui->sumDataPlot->legend->setVisible(true);
    QFont legendFont = font();  // start out with MainWindow's font..
    legendFont.setPointSize(6); // and make a bit smaller for legend
    ui->sumDataPlot->legend->setFont(legendFont);
    ui->sumDataPlot->legend->setBrush(QBrush(QColor(255,255,255,230)));
    ui->sumDataPlot->axisRect()->insetLayout()->setInsetAlignment(0, Qt::AlignTop|Qt::AlignRight);
    ui->sumDataPlot->rescaleAxes();
}

void MainWindow::setupFourQuad(){
    ui->fourQuadPlot->xAxis->setRange(-1, 1);
    ui->fourQuadPlot->yAxis->setRange(-1, 1);
    ui->fourQuadPlot->yAxis->setLabel("光斑坐标");

    ui->fourQuadPlot->addGraph();
    ui->fourQuadPlot->graph(0)->setLineStyle(QCPGraph::lsLine);
    ui->fourQuadPlot->graph(0)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, 10));

    ui->fourQuadPlot->xAxis2->setVisible(true);
    ui->fourQuadPlot->xAxis2->setTickLabels(false);
    ui->fourQuadPlot->yAxis2->setVisible(true);
    ui->fourQuadPlot->yAxis2->setTickLabels(false);

    connect(ui->fourQuadPlot->xAxis, SIGNAL(rangeChanged(QCPRange)),
            ui->fourQuadPlot->xAxis2, SLOT(setRange(QCPRange)));
    connect(ui->fourQuadPlot->yAxis, SIGNAL(rangeChanged(QCPRange)),
            ui->fourQuadPlot->yAxis2, SLOT(setRange(QCPRange)));
}

void MainWindow::connectSlots(){
    connect(ui->rawDataPlot, SIGNAL(selectionChangedByUser()), this, SLOT(selectionChanged()));
    connect(ui->rawDataPlot, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(mousePress()));
    connect(ui->rawDataPlot, SIGNAL(mouseWheel(QWheelEvent*)), this, SLOT(mouseWheel()));
    connect(this->dialog, &BaudRateDialog::customBaudRateSet, this, [=](int value){
        qDebug() << value;
        this->baudRate = value;
    });
}


void MainWindow::selectionChanged(){

    qDebug() << "selectionChanged";
}
void MainWindow::mousePress(){
}
void MainWindow::mouseWheel(){
}

void MainWindow::saveData(const QString &filename, const QVector<double> &data, SaveFormat format)
{
    if(format == SaveFormat::BINFormat){
        saveDataBIN(filename + ".bin", data);
    }
    else{
        saveDataCSV(filename + ".csv", data);
    }
}

static const double multiplier = 0.000152;
void MainWindow::saveDataBIN(const QString& filename, const QVector<double> &data){
    QDateTime now = QDateTime::currentDateTime();
    QDir dir = QDir(saveDataPathPrefix);
    QFile file = dir.filePath(filename);
    if(!file.open(QIODevice::WriteOnly | QIODevice::Append)){
        return;
    }
    char *buffer = (char *)malloc(data.size() * 2);
    for(int i = 0; i < data.size(); i ++){
        short value = static_cast<short>(data.at(i) / multiplier);
        buffer[2 * i] = value & 0xFF;
        buffer[2 * i + 1] = (value >> 8) & 0xFF;

    }
    file.write(buffer, data.size() * 2);
    free(buffer);
    file.close();
}

void MainWindow::saveDataCSV(const QString& filename, const QVector<double> &data){
    QDir dir = QDir(saveDataPathPrefix);
    QFile file = dir.filePath(filename);
    qDebug() << saveDataPathPrefix;
    qDebug() << filename;
    if(!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append))
        return;


    QTextStream out(&file);
    out.setRealNumberPrecision(5); // Set precision to 5 digits
    out.setRealNumberNotation(QTextStream::FixedNotation);

    for(int i = 0; i < data.size(); ++i) {
        out << data[i];
        out << '\n';
    }
    file.close();
}


void MainWindow::displayAllSerialPortInfo() {
    QList<QSerialPortInfo> ports = QSerialPortInfo::availablePorts();

    for(int i =0; i < ports.size(); i ++){
        const QSerialPortInfo &info = ports.at(i);
        qDebug() << "index of current ports " << i;
        qDebug() << "description: " << info.description();
        qDebug() << "name: " << info.portName();
        qDebug() << "systemLocation: " << info.systemLocation();
        qDebug() << "manufacturer: " << info.manufacturer();
        qDebug() << "serialNumber: " << info.serialNumber();
    }
}

void MainWindow::displayHexAndDecodeFloats(const char *buf, qint64 size) {
    char buf1[100];
    for(qint64 i = 0; i < size; i ++  ){
        qsnprintf(buf1, 100, "%X", buf[i]);
        qDebug() << i << " " << buf1;
    }
}

void MainWindow::on_baudRate_currentTextChanged(const QString &arg1)
{
    qDebug() << arg1;
    if(arg1 != "自定义"){
        this->baudRate = arg1.toInt();
    }else{
    this->dialog->show();
    }
}


void MainWindow::on_portNames_currentTextChanged(const QString &arg1)
{
    qDebug() << arg1;
}

